package net.zoneland.knowledge.service.impl;

import cn.hutool.core.io.file.FileNameUtil;
import com.google.common.base.Preconditions;
import net.zoneland.knowledge.constant.SdkConstant;
import net.zoneland.knowledge.dao.AttachmentPreviewAppKeyMapper;
import net.zoneland.knowledge.dao.WorkApplyInfoMapper;
import net.zoneland.knowledge.exceptions.WorkSharedEditingException;
import net.zoneland.knowledge.model.*;
import net.zoneland.knowledge.service.AttachmentService;
import net.zoneland.knowledge.service.OaDocsSdkService;
import net.zoneland.knowledge.service.SharedEditingFolderService;
import net.zoneland.knowledge.utils.OplogUtils;
import net.zoneland.oadocs.sdk.enums.FileTypeEnums;
import net.zoneland.oadocs.sdk.pojo.*;
import net.zoneland.oadocs.sdk.service.impl.DocServiceClient;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

@Service
public class OaDocsSdkServiceImpl implements OaDocsSdkService {

    private final static Logger LOGGER = LoggerFactory.getLogger(OaDocsSdkServiceImpl.class);

    private static final ZoneId zoneId = ZoneId.systemDefault();

    /**
     * 文档服务.
     */
    @Autowired
    private DocServiceClient docServiceClient;

    /**
     * 附件服务
     */
    @Autowired
    private AttachmentService attachmentService;

    /**
     * 文件夹服务.
     */
    @Autowired
    private SharedEditingFolderService sharedEditingFolderService;

    /**
     * 查询appkey.
     */
    @Autowired
    private AttachmentPreviewAppKeyMapper previewAppKeyMapper;

    /**
     * 工单信息数据接口.
     */
    @Autowired
    private WorkApplyInfoMapper workApplyInfoMapper;

    /**
     * 构建访问文档协作平台的key.
     * 在有效期内直接返回
     * 失效了要调接口去查询新的appkey
     * @return appkey
     */
    @Override
    public String getAppKey() throws WorkSharedEditingException {
        final AttachmentPreviewAppKey appKeyInfo = queryAppKeyFromDataBase();
        final String dbAppkey = appKeyInfo.getAppKey();
        return isTimeOut(appKeyInfo.getCreateTime()) ? getNewAppKey(dbAppkey, appKeyInfo.getId()) : dbAppkey;
    }


    /**
     * 从数据库查询最新的appkey.
     * @return 返回最新的appkey信息对象.
     * @throws Exception 没查到的时候抛出异常.
     */
    private AttachmentPreviewAppKey queryAppKeyFromDataBase() throws WorkSharedEditingException {
        return Optional.ofNullable(previewAppKeyMapper.selectAppKey())
                .orElseThrow(() ->new WorkSharedEditingException("queryAppKeyFromDataBase from database error"));
    }

    /**
     * 调用oa办公协作sdk的接口查询新的appkey
     * @param oldAppKey 老的appkey
     * @param appKeyBaseId appkey表数据库id
     * @return 返回最新的appkey.
     */
    private String getNewAppKey(final String oldAppKey, final String appKeyBaseId) throws WorkSharedEditingException {
        //根据旧的appkey去oa文档协作系统获取最新的appkey
        final DocResult<String>  appKeyNew = docServiceClient.getAppKey(oldAppKey);
        if(appKeyNew.getCode() != SdkConstant.SUCCESS){
            LOGGER.error("get new appkey error,code:[{}],msg:[{}],data:[{}]",
                    appKeyNew.getCode(), appKeyNew.getMsg(), appKeyNew.getData());
            throw new WorkSharedEditingException("getNewAppKey error . old appKey is [" + oldAppKey + "], " +
                    "base id is [" + appKeyBaseId + "],query new appKey return is [" + appKeyNew + "]");
        }
        //获取返回的appkey.
        final String newAppKey = appKeyNew.getData();
        //更新数据库appkey信息.
        saveAndUpdate(newAppKey, appKeyBaseId);
        //返回最新的appkey.
        return newAppKey;
    }

    /**
     * 更新数据库appkey的信息.
     * @param appkey 调用oa文档协作系统的key
     * @param appKeyBaseId 数据库保存appkey信息的id.
     */
    @Transactional
    void saveAndUpdate(final String appkey, final String appKeyBaseId){
        final AttachmentPreviewAppKey previewAppKey = new AttachmentPreviewAppKey();
        previewAppKey.setAppKey(appkey);
        previewAppKey.setStatus(1);
        previewAppKey.setId(OplogUtils.getUuid32());
        //新的key保存
        previewAppKeyMapper.insert(previewAppKey);
        //标记该条appkey信息为失效
        previewAppKeyMapper.updateByPrimaryKey(appKeyBaseId);
    }

    /**
     * 是否已超时.
     * @param createTime 该条appkey生成时间.
     * @return 返回是否超时（2个月超时）.
     */
    private boolean isTimeOut(final Date createTime) {
        return LocalDateTime.ofInstant(createTime.toInstant(), zoneId)
                .isBefore(LocalDateTime.now().minusMonths(SdkConstant.APP_KEY_TIME_OUT));
    }

    /**
     * 上传文件.
     *
     * @param paramList 文件信息列表
     * @param fileList  文件名列表.
     * @return 返回文件上传生成的信息.
     */
    @Override
    public List<DocDetailInfo> upload(final List<DocUploadRequest> paramList, final List<DocFileRequest> fileList) {
        final DocResult<List<DocDetailInfo>> docResult = docServiceClient.upload(getAppKey(), paramList, fileList);
        if(docResult.getCode() != SdkConstant.SUCCESS){
            LOGGER.error("uploadFile error,paramList:[{}],fileList:[{}]", paramList, fileList);
            throw new WorkSharedEditingException("upload file error , param is []" + paramList + ",file is []" + fileList);
        }
        return docResult.getData();
    }

    /**
     * 上传附件.
     * @param workApplyInfoAttachment 工单附件信息.
     * @return 返回调用oa文档协作信息文件上传接口成功还是失败，以及相关的文件信息.
     */
    @Override
    public void uploadFile(final WorkApplyInfoAttachment workApplyInfoAttachment){
        // 如果没有工单附件数据，则不用上传
        if(Objects.isNull(workApplyInfoAttachment.getAttachment())) {
            return;
        }
        //文件上传(文件夹信息，文件信息)
        final List<DocDetailInfo> docResult = upload(Arrays.asList(fillDocUpLoadRequest(workApplyInfoAttachment)),
                Arrays.asList(fillDocFileRequest(workApplyInfoAttachment)));
        //文档协作系统提供的接口返回的数据是个list，我们这边业务只有一条，所以findfirst
        final String id = docResult.stream()
                .map(info -> StringUtils.trimToEmpty(info.getId()))
                .filter(StringUtils::isNotBlank)
                .findFirst()
                .orElse(StringUtils.EMPTY);
        //存到数据库(工单附件id， wps返回的文件id)
        saveWpsFileId(workApplyInfoAttachment.getId(), id);
    }

    /**
     * 存wps文件id.
     * @param attachmentId 附件信息表id
     * @param wpsFileId wps返回的文件id
     */
    private void saveWpsFileId(final String attachmentId, final String wpsFileId){
        Optional.ofNullable(wpsFileId).ifPresent(it -> updateWorkAttachmentWpsFileId(attachmentId, it));
    }

    /**
     * 构建文件夹参数（只是上传的文件）.
     * @param workApplyInfoAttachment 工单附件信息.
     * @return 返回文件夹参数对象.
     */
    private DocUploadRequest fillDocUpLoadRequest(final WorkApplyInfoAttachment workApplyInfoAttachment) {
        final DocUploadRequest docUploadRequest = new DocUploadRequest();
        //该接口只做上传不更新，所以类型false
        docUploadRequest.setUpdate(false);
        //附件名称
        final String fileName = workApplyInfoAttachment.getFileName();
        docUploadRequest.setFileName(fileName);
        //每个工单附件一个父文件夹
        docUploadRequest.setParentId(queryFileId(workApplyInfoAttachment.getSubTaskId()+FileNameUtil.getPrefix(fileName)));
        return docUploadRequest;
    }

    /**
     * 构建文件参数.
     * @param info 工单附件信息.
     * @return 返回附件上传的参数对象.
     */
    private DocFileRequest fillDocFileRequest(final WorkApplyInfoAttachment info) {
        final DocFileRequest docFileRequest = new DocFileRequest();
        docFileRequest.setFileName(info.getFileName());
        docFileRequest.setInputStream(new ByteArrayInputStream(info.getAttachment()));
        return docFileRequest;
    }

    /**
     * 查询文件id.
     * @param folderName
     * @return
     */
    private String queryFileId(final String folderName){
        //根据文件名查询文件夹信息
        final SharedEditingFolderInfo folderInfo = sharedEditingFolderService.queryFolder(folderName);
        //如果该文件存在直接返回文件夹id
        if(Objects.nonNull(folderInfo) && isFolderExist(folderInfo.getFileId())) {
            return folderInfo.getFileId();
        }
        //不存在的话就去创建文件夹
        return createFolderByFolderName(folderName);
    }

    /**
     * 创建空文件夹、空目录.
     * @param folderName 文件名.
     * @return 返回文件id.
     */
    public String createFolderByFolderName(final String folderName) {
        final DocResponseInfo docResponseInfo = createFolder(folderName);
        sharedEditingFolderService.saveOrUpdateFolder(docResponseInfo);
        return docResponseInfo.getFileId();
    }

    /**
     * 该文件是否已创建.
     * @param folderId 文件夹id.
     * @return 返回是否已存在boolean
     */
    private boolean isFolderExist(final String folderId){
        return docServiceClient.getFolderById(getAppKey(), folderId).getCode() == SdkConstant.SUCCESS;
    }

    /**
     *
     * 获取在线编辑链接.
     * @param userId 用户id
     * @param id 附件id
     * @return 返回获取到的在线编辑对象信息.
     * @throws IllegalArgumentException 调用oa文档协作系统获取在线编辑接口可能会抛出的异常
     */
    @Override
    public DocResult<String> queryOnlineEditClientLink(final String userId, final String id) throws IllegalArgumentException {
        //根据id获取工单附件信息
        final WorkApplyInfoAttachment info = getAttachmentInfoById(id);
        //工单附件不为空并且已经上传到oa协作平台上
        Preconditions.checkArgument(Objects.nonNull(info) && StringUtils.isNotBlank(info.getWpsFileId())
                , "工单附件不存在或者没有上传到wps协作平台");
        //调用oa文档协作系统接口去获取在线编辑的链接（用户id， 调用oa文档协作平台需要的key， 文件id， 文件拓展名， 权限（只读还是可编辑），是否开启直接编辑 ，水印（默认））
        return docServiceClient.getOnlineEditClientLink(userId, getAppKey(), info.getWpsFileId(),
                getExtensionByFileName(info.getFileName()), SdkConstant.PERMISSION_WRITE, SdkConstant.OPEN_EDIT, new PreviewRequest());
    }

    /**
     * 根据id查询附件信息.
     * @param id 附件id.
     * @return 附件信息.
     */
    private WorkApplyInfoAttachment getAttachmentInfoById(final String id) {
        return workApplyInfoMapper.findWorkApplyInfoAttachmentsById(id);
    }

    /**
     * 根据文件名获取文件拓展名称.
     * @param filename 文件名.
     * @return 文件拓展名.
     */
    private String getExtensionByFileName(final String filename) {
        return FileNameUtil.getSuffix(filename);
    }
    /**
     * 查询下载链接.
     *
     * @param fileId 附件id
     * @return 返回的下载链接有限期一小时，过期要重新获取
     */
    @Override
    public DocResult<String> queryDownloadLink(final String fileId) throws IllegalArgumentException  {
        return docServiceClient.getDownloadLink(getAppKey(), fileId);
    }

    /**
     * 创建空文件夹.
     *
     * @param folderName 文件名称.
     * @return 返回创建的文件信息.
     */
    @Override
    public DocResponseInfo createFolder(final String folderName) throws WorkSharedEditingException {
        final DocCreateRequest createRequest = new DocCreateRequest();
        createRequest.setFileName(folderName);
        createRequest.setFileType(FileTypeEnums.FOLDER.getValue());
        final DocResult<DocResponseInfo> docResult = docServiceClient.createFileOrFolder(getAppKey(), createRequest);
        if(docResult.getCode() != SdkConstant.SUCCESS) {
            LOGGER.error("create folder error ,folderName is [{}], return msg is [{}]", folderName, docResult.getMsg());
            throw new WorkSharedEditingException("OaDocsSdkServiceImpl createFolder error," +
                    "folderName is [" + folderName +"], msg is [" + docResult.getMsg() +"]");
        }
        return docResult.getData();
    }

    /**
     * 初始化工单模板附件信息.
     *
     * @param request
     * @param info 工单信息对象
     * @param user 用户
     */
    @Override
    public void initWorkApplyInfoTemplateAttachments(final HttpServletRequest request, final WorkApplyInfo info, final LoginUserPojo user) {
        final MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        //初始化模板附件信息
        attachmentService.handleTemplateAttachments(info, user, multipartRequest);
    }

    /**
     * 更新附件表的wpsFileId.
     *
     * @param id
     * @param wpsFileId
     */
    @Override
    public void updateWorkAttachmentWpsFileId(final String id, final String wpsFileId) {
        workApplyInfoMapper.updateAttachmentWpsFileId(id, wpsFileId);
    }

    /**
     * 更新附件表信息.
     *
     * @param workApplyInfoAttachment
     */
    @Override
    public void updateWorkAttachment(final WorkApplyInfoAttachment workApplyInfoAttachment) {
        workApplyInfoMapper.updateAttachmentByWpsFileId(workApplyInfoAttachment);
    }

    /**
     * 激活用户账号.
     *
     * @param userIds
     */
    @Override
    public void activeAccountBatch(final List<String> userIds) {
        final DocResult<String>  docResult = docServiceClient.batchActiveAccount(getAppKey(), userIds);
        if(docResult.getCode() != SdkConstant.SUCCESS){
            LOGGER.error("active Account Batch error ,userIds is [{}], return msg is [{}]", userIds, docResult.getMsg());
            throw new WorkSharedEditingException("active account error! userId is :["+ userIds + "], return msg is [" + docResult.getMsg() + "]");
        }
    }

}
